package org.orcas.commons.collections;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

import org.apache.commons.collections.Bag;
import org.orcas.commons.collections.util.CollectionFactory;
import org.orcas.commons.collections.util.CollectionKind;

@SuppressWarnings("unchecked")
public class CollectionUtils extends org.apache.commons.collections.CollectionUtils {
	
	
	public static Collection asSet(Collection c){
    	return createNewSet(c);
    }
    
    public static Collection asBag(Collection c){
    	return createNewBag(c);
    }
    
    public static Collection asOrderedSet(Collection c){
    	return createNewOrderedSet(c);
    }
    
    public static Collection asSequence(Collection c){
    	return createNewSequence(c);
    }
    
	/**
     * Returns the object at the supplied position of the supplied ordered
     * collection
     * 
     * @param c
     *            the collection
     * @param i
     *            the position.
     * @return the object in s at position i (or null if there is no object at
     *         that position).
     * 
     * Note: OCL is 1-indexed!
     */
    public static Object at(Collection c, int i) {
        int curr = 0;
        for (Iterator it = c.iterator(); it.hasNext();) {
            Object o = it.next();
            if (curr++ == i - 1)
                return o;
        }
        return null; // undefined
    }
    
    /**
     * Returns the first element of the supplied ordered collection if the
     * ordered collection is not empty, null otherwise.
     * 
     * @param c
     *            the collection
     * @return the first element of the collection or null.
     */
    public static Object first(Collection c) {
        if (c.isEmpty())
            return null; // undefined
        return c.iterator().next();
    }
    
	/**
     * Flattens a collection by taking the union of each of its constituent
     * collections, if they are a collection type, or just returns the supplied
     * collection if they are not.
     * 
     * @param c
     *            the collection to flatten
     * @return the flattened collection
     */
	public static Collection flatten(Collection c) {
        if (c.isEmpty())
            return c;

        Iterator it = c.iterator();
        Object o = it.next();

        // if the element type is not a collection type, the result is the
        // supplied collection.
        if (!(o instanceof Collection))
            return c;

        Collection result = null;
        if (c instanceof Bag)
            result = createNewBag();
        else if (c instanceof Set)
            result = createNewSet();
        else
            // Sequence
            result = createNewSequence();

        // the element type is a collection type -- flatten
        for (it = c.iterator(); it.hasNext();)
            result.addAll( (Collection) it.next());

        return result;
    }
    
	/**
     * Prepends the supplied object to the supplied ordered collection
     * 
     * @param c
     *            the collection
     * @param o
     *            the object
     * @return the supplied ordered collection with the new element at the
     *         beginning
     */
    public static Collection prepend(Collection c, Object o) {
        Collection result = null;
        if (c instanceof LinkedHashSet)
            result = createNewOrderedSet();
        else
            result = createNewSequence();
        result.add(o);
        result.addAll(c);
        return result;
    }
    
    /**
     * Returns the index in the supplied collection of the supplied object or
     * null if the object is not in the collection.
     * 
     * @param c
     *            the collection.
     * @param o
     *            the object.
     * @return the index (1-based) of the object in the collection.
     */
    public static Integer indexOf(Collection c, Object o) {
        int index = 1;
        for (Iterator it = c.iterator(); it.hasNext();) {
            if (o.equals(it.next()))
                return new Integer(index);
            index++;
        }
        return null; // undefined
    }
    
    /**
     * Inserts the supplied object at the supplied position of the supplied
     * ordered collection
     * <p>
     * <b>Note:</b> OCL is 1-indexed!
     * </p>
     * 
     * @param c
     *            the collection.
     * @param oclIndex
     *            the position to insert at.
     * @param o
     *            the object to insert.
     * @return the ordered collection with the object inserted at the position.
     */
    public static Collection insertAt(Collection c, int oclIndex, Object o) {
        int index = oclIndex - 1;
        Collection result = null;
        if (c instanceof LinkedHashSet)
            result = createNewOrderedSet();
        else
            result = createNewSequence();
        if (index - 1 < 0 || index > c.size())
            return null; // undefined
        int curr = 0;
        for (Iterator it = c.iterator(); it.hasNext();) {
            if (curr == index)
                result.add(o);
            result.add(it.next());
            curr++;
        }
        return result;
    }
    
    /**
     * Returns the last element of the supplied collection if the collection is
     * not empty, null otherwise.
     * 
     * @param c
     *            the collection
     * @return the last element of the collection or null.
     */
    public static Object last(Collection c) {
        if (c.isEmpty())
            return null; // undefined
        Object result = null;
        for (Iterator it = c.iterator(); it.hasNext();)
            result = it.next();
        return result;
    }
    
    
    /**
     * Returns the subset of the supplied ordered collection whose elements have
     * indices between the supplied lower and upper bounds (inclusive).
     * 
     * @param c
     *            the collection
     * @param oclLower
     *            the lower bound.
     * @param oclUpper
     *            the upper bound.
     * @return the ordered sub-collection between the lower and upper bounds.
     */
    public static Collection subOrderedSet(Collection c, int oclLower,
            int oclUpper) {
        int lower = oclLower - 1;
        int upper = oclUpper - 1;
        if (lower < 0 || upper >= c.size())
            return null; // undefined
        Collection result = null;
        if (c instanceof LinkedHashSet)
            result = createNewOrderedSet();
        else
            result = createNewSequence();
        int curr = 0;
        for (Iterator it = c.iterator(); it.hasNext();) {
            Object o = it.next();
            if (curr >= lower && curr <= upper)
                result.add(o);
            curr++;
        }
        return result;
    }

    /**
     * Returns the subsequence of the supplied ordered collection whose elements
     * have indices between the supplied lower and upper bounds (inclusive).
     * 
     * @param c
     *            the collection
     * @param oclLower
     *            the lower bound.
     * @param oclUpper
     *            the upper bound.
     * @return the subsequence between the lower and upper bounds.
     */
    public static Collection subSequence(Collection c, int oclLower,
            int oclUpper) {
        int lower = oclLower - 1;
        int upper = oclUpper - 1;
        if (lower < 0 || upper >= c.size())
            return null; // undefined
        Collection result = createNewSequence();
        int curr = 0;
        for (Iterator it = c.iterator(); it.hasNext();) {
            Object o = it.next();
            if (curr >= lower && curr <= upper)
                result.add(o);
            curr++;
        }
        return result;
    }
    
    /**
     * Returns the sum of all elements of the supplied collection if the collection is
     * not empty and its elements is a instance of Integer or Double, null otherwise.
     * 
     * @param c
     *            the collection
     * @return the size of the collection or null.
     */
    public static Double sum(Collection c) {
      
    	if (c.isEmpty())
            return null; // undefined
      
        Double result = 0.0;
        for (Iterator it = c.iterator(); it.hasNext();){
            Object next = it.next();

            result += (next instanceof Integer) ? (Integer) next : (Double) next; 
        }
        return result;
    }
    
    /**
     * Creates a new java set
     */
    
    public static Collection createNewSet() {
        return CollectionFactory.INSTANCE.createCollection(CollectionKind.SET);
    }
    
    public static Collection createNewSet(Collection c) {
        return CollectionFactory.INSTANCE.createCollection(CollectionKind.SET, c);
    }

    /**
     * Creates a new bag
     */
    public static Collection createNewBag() {
    	return CollectionFactory.INSTANCE.createCollection(CollectionKind.BAG);
    }
    
    public static Collection createNewBag(Collection c) {
    	return CollectionFactory.INSTANCE.createCollection(CollectionKind.BAG, c);
    }

    /**
     * Creates a new java ordered set
     */
    public static Collection createNewOrderedSet() {
    	return CollectionFactory.INSTANCE.createCollection(CollectionKind.ORDERED_SET);
    }

    public static Collection createNewOrderedSet(Collection c) {
    	return CollectionFactory.INSTANCE.createCollection(CollectionKind.ORDERED_SET, c);
    }
    
    /**
     * Creates a new sequence
     */
    public static Collection createNewSequence() {
    	return CollectionFactory.INSTANCE.createCollection(CollectionKind.SEQUENCE);
    }
    
    public static Collection createNewSequence(Collection c) {
    	return CollectionFactory.INSTANCE.createCollection(CollectionKind.SEQUENCE, c);
    }
    
}
